Skip to content

Fix calendar, fix page search, fix grouped reactions PFPs#2183

Merged
prxt6529 merged 14 commits intomainfrom
fix-calendar-fix-page-search
Mar 31, 2026
Merged

Fix calendar, fix page search, fix grouped reactions PFPs#2183
prxt6529 merged 14 commits intomainfrom
fix-calendar-fix-page-search

Conversation

@prxt6529
Copy link
Copy Markdown
Collaborator

@prxt6529 prxt6529 commented Mar 31, 2026

Summary by CodeRabbit

  • New Features

    • Token-based canonical page search for more accurate, prioritized results.
    • Default title for the meme calendar route; improved title updates for route/wave changes.
    • Notification grouping UI now shows distinct single-reactor vs multi-reactor presentations.
  • Bug Fixes

    • Timezone-safe month/year calendar formatting.
    • Improved avatar/profile-picture fallbacks, identity deduplication, and warnings for missing identities.
  • Tests

    • Expanded tests for notifications, header search behaviors, calendar timezone formatting, and title context.

Signed-off-by: prxt6529 <prxt@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds UTC-aware month formatting and tests; refactors notification identity deduplication and adds a single-reactor render path; replaces page-search priority with canonical token matching; consolidates TitleContext route/title effects and default for /meme-calendar; and adds/updates related tests.

Changes

Cohort / File(s) Summary
Meme-calendar UTC formatting
components/meme-calendar/meme-calendar.helpers.tsx, components/meme-calendar/MemeCalendar.tsx, __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
Exported formatUtcMonth and formatUtcMonthYear; replaced local month/year formatting in MemeCalendar with UTC helpers and added a timezone-isolated test.
Notification deduplication & rendering
components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx, __tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
Added identity normalization (getNonEmptyIdentityValue, getIdentityKey), mergeProfiles, and changed grouping to map latest+merged identity by normalized key (with unknown-identity-* fallback and console.warn). Switched aggregation payload to { latest, identity }, changed sort to use created_at then id, and added a single-reactor rendering branch (NotificationHeader + follow-one) with tests covering dedupe, avatar keys, blank identities, and aliasing.
Page search canonical token matching
components/header/header-search/HeaderSearchModal.tsx, __tests__/components/header/HeaderSearchModal.test.tsx
Replaced legacy priority logic with token-based canonical matching (singularize/tokenize), added canonical exact/in-order/prefix predicates, compositeValues generation, and updated scoring to mix legacy substring signals with canonical token checks; extended tests for pluralization, partial tokens, breadcrumb+title queries, and href-priority ordering.
TitleContext refactor & defaults
contexts/TitleContext.tsx, __tests__/contexts/TitleContext.test.tsx
Added /meme-calendar default title; lazily initialize title from route; consolidated pathname/searchParams effects into one effect using refs; gate wave param derivation to wave routes; and updated tests asserting title/document.title transitions on route and wave-id changes.
Tests — header/title/notifications
__tests__/components/header/HeaderSearchModal.test.tsx, __tests__/contexts/TitleContext.test.tsx, __tests__/components/brain/notifications/...
Added and expanded tests exercising new search matching, TitleContext behavior, notification deduplication including unknown-identity warnings, and meme-calendar timezone formatting.

Sequence Diagram(s)

sequenceDiagram
  participant UI as rgba(63,81,181,0.5) NotificationDropReactedGroup
  participant Logic as rgba(33,150,243,0.5) DeduplicationLogic
  participant Avatars as rgba(76,175,80,0.5) OverlappingAvatars
  participant Header as rgba(255,152,0,0.5) NotificationHeader
  participant FollowAll as rgba(156,39,176,0.5) NotificationsFollowAllBtn
  participant FollowOne as rgba(233,30,99,0.5) NotificationsFollowBtn
  participant Timestamp as rgba(121,85,72,0.5) NotificationTimestamp

  UI->>Logic: provide related_notifications list
  Logic-->>UI: grouped latestPerUser + merged identities
  alt multiple visible reactors
    UI->>Avatars: render avatar items (keys from getIdentityKey)
    UI->>FollowAll: render follow-all control
    UI->>Timestamp: render timestamp
  else single visible reactor
    UI->>Header: render NotificationHeader with single reactor author
    UI->>FollowOne: render single follow button
    UI->>Timestamp: render timestamp and "reacted" label
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ragnep
  • simo6529

Poem

🐰
I hopped through dates in UTC light,
Merged many faces into one sight,
Search learned to dance on tokens and cue,
Titles now follow the route true,
Tests cheer — a rabbit's delight! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the three main fixes in the changeset: calendar utilities refactoring, page search ranking improvements, and grouped reaction avatar identity handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-calendar-fix-page-search

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx (1)

42-44: Consider using consistent nullish operators for clarity.

handle uses ?? while pfp and primary_address use ||. This means an empty string handle would be kept, but an empty string pfp or primary_address would fall back. If this is intentional, a brief comment would help future readers understand the distinction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`
around lines 42 - 44, The three fields in NotificationDropReactedGroup mapping
use mixed fallback operators—handle uses nullish coalescing (preferred.handle ??
fallback.handle) while pfp and primary_address use logical OR (preferred.pfp ||
fallback.pfp, preferred.primary_address || fallback.primary_address)—so decide
whether empty strings should be treated as valid values; either switch pfp and
primary_address to nullish coalescing to match handle (preferred.pfp ??
fallback.pfp, preferred.primary_address ?? fallback.primary_address) or keep the
current operators but add a concise comment above these lines explaining why
handle preserves empty strings while pfp/primary_address fall back on falsy
values.
__tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx (1)

102-124: Consider creating properly typed mock data instead of as never.

The as never assertion hides missing fields from ApiProfileMin (e.g., banner1_color, cic, rep, tdh, level). While the component may not access these fields, using proper types would catch issues if the component evolves.

♻️ Example: Create a minimal mock factory
function createMockProfile(overrides: Partial<ApiProfileMin> & { handle: string }): ApiProfileMin {
  return {
    id: `${overrides.handle}-id`,
    handle: overrides.handle,
    pfp: null,
    banner1_color: null,
    banner2_color: null,
    cic: 0,
    rep: 0,
    tdh: 0,
    tdh_rate: 0,
    xtdh: 0,
    xtdh_rate: 0,
    level: 0,
    primary_address: `0x${overrides.handle}`,
    subscribed_actions: [],
    ...overrides,
  };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@__tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx`
around lines 102 - 124, The test currently returns a mock profile object cast
with "as never", which hides missing fields from the ApiProfileMin type; replace
this with a minimal, correctly typed mock factory (e.g., createMockProfile) that
returns ApiProfileMin and accepts overrides (Partial<ApiProfileMin> & { handle:
string }), populate required fields like id, handle, pfp, banner1_color,
banner2_color, cic, rep, tdh, tdh_rate, xtdh, xtdh_rate, level, primary_address,
subscribed_actions, and spread overrides; then use createMockProfile(...) in
place of the object and remove the "as never" cast so TypeScript will surface
any missing properties in functions/components that consume the mock (refer to
the returned object in this test and to ApiProfileMin).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@__tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts`:
- Around line 34-36: The afterEach cleanup is restoring process.env.TZ by
assigning originalTz directly, which writes the string "undefined" when
originalTz was unset; modify the afterEach in
meme-calendar.helpers.timezone.test.ts to check originalTz: if it is undefined
delete process.env.TZ, otherwise set process.env.TZ = originalTz, using the
existing afterEach and originalTz identifiers to locate the change.

In `@components/header/header-search/HeaderSearchModal.tsx`:
- Around line 161-172: The plural-singular logic in HeaderSearchModal's token
normalization over-stems by treating any token ending with "ses" as removable;
update the condition in the function that checks token suffixes (the branch that
currently tests token.endsWith("ses")) to a narrower match such as
token.endsWith("sses") so words like "messages" or "cases" are not truncated
incorrectly, leaving the general token.endsWith("s") && !token.endsWith("ss")
fallback to remove simple trailing "s" cases.

In `@components/meme-calendar/meme-calendar.helpers.tsx`:
- Around line 616-623: The month formatting in formatUtcMonth currently calls
d.toLocaleString(undefined, ...) which uses the runtime default locale and can
cause SSR/CSR hydration mismatches; update the function so the toLocaleString
call uses an explicit locale (e.g., 'en-US') or add an optional locale parameter
defaulting to a fixed locale, ensuring the call to d.toLocaleString(...) passes
that explicit locale along with month/style and timeZone: 'UTC' (change inside
function formatUtcMonth to remove undefined and use the explicit locale or
param).

In `@contexts/TitleContext.tsx`:
- Around line 123-126: The current branch only resets the title and waveData
when a wave id disappears; change it to also reset when the wave id changes by
comparing the previous and current wave ids (not just their truthiness). In the
block that uses previousWaveInUrl and currentWaveInUrl, add a condition that
checks previousWaveId !== currentWaveId (derived from the query param used to
build previousWaveInUrl/currentWaveInUrl) and, when they differ, call
setTitle(getDefaultTitleForRoute(pathname)) and setWaveData(null) (same actions
as the disappearance case) so the old wave title is cleared whenever the active
wave id swaps. Ensure you reference the existing variables previousWaveInUrl,
currentWaveInUrl, previousWaveId/currentWaveId, setTitle, setWaveData,
getDefaultTitleForRoute, and pathname when applying the change.

---

Nitpick comments:
In
`@__tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx`:
- Around line 102-124: The test currently returns a mock profile object cast
with "as never", which hides missing fields from the ApiProfileMin type; replace
this with a minimal, correctly typed mock factory (e.g., createMockProfile) that
returns ApiProfileMin and accepts overrides (Partial<ApiProfileMin> & { handle:
string }), populate required fields like id, handle, pfp, banner1_color,
banner2_color, cic, rep, tdh, tdh_rate, xtdh, xtdh_rate, level, primary_address,
subscribed_actions, and spread overrides; then use createMockProfile(...) in
place of the object and remove the "as never" cast so TypeScript will surface
any missing properties in functions/components that consume the mock (refer to
the returned object in this test and to ApiProfileMin).

In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 42-44: The three fields in NotificationDropReactedGroup mapping
use mixed fallback operators—handle uses nullish coalescing (preferred.handle ??
fallback.handle) while pfp and primary_address use logical OR (preferred.pfp ||
fallback.pfp, preferred.primary_address || fallback.primary_address)—so decide
whether empty strings should be treated as valid values; either switch pfp and
primary_address to nullish coalescing to match handle (preferred.pfp ??
fallback.pfp, preferred.primary_address ?? fallback.primary_address) or keep the
current operators but add a concise comment above these lines explaining why
handle preserves empty strings while pfp/primary_address fall back on falsy
values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07764220-1ebc-42a8-9e22-e5ed4a2a8657

📥 Commits

Reviewing files that changed from the base of the PR and between c51b6cc and 3459faf.

📒 Files selected for processing (9)
  • __tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • __tests__/components/header/HeaderSearchModal.test.tsx
  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
  • __tests__/contexts/TitleContext.test.tsx
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
  • components/header/header-search/HeaderSearchModal.tsx
  • components/meme-calendar/MemeCalendar.tsx
  • components/meme-calendar/meme-calendar.helpers.tsx
  • contexts/TitleContext.tsx

Comment thread components/header/header-search/HeaderSearchModal.tsx
Comment thread components/meme-calendar/meme-calendar.helpers.tsx
Comment thread contexts/TitleContext.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
contexts/TitleContext.tsx (1)

108-109: Optional cleanup: remove redundant wave-id aliases.

currentWaveId/previousWaveId are direct aliases and can be inlined in the condition to reduce cognitive overhead.

Suggested simplification
-    const currentWaveId = currentWaveInUrl;
-    const previousWaveId = previousWaveInUrl;
...
-    if (
-      previousWaveInUrl &&
-      (!currentWaveInUrl || previousWaveId !== currentWaveId)
-    ) {
+    if (
+      previousWaveInUrl &&
+      (!currentWaveInUrl || previousWaveInUrl !== currentWaveInUrl)
+    ) {

Also applies to: 125-128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contexts/TitleContext.tsx` around lines 108 - 109, Remove the redundant
aliases currentWaveId and previousWaveId in TitleContext.tsx and inline
currentWaveInUrl and previousWaveInUrl directly where those aliases are used
(e.g., in the conditional checks around the currentWave/previousWave logic and
the block referenced near lines 125-128). Update any expressions or conditions
that reference currentWaveId/previousWaveId to use
currentWaveInUrl/previousWaveInUrl instead and delete the now-unused const
declarations to reduce cognitive overhead and unused variables.
components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx (2)

26-33: Empty string edge case may cause unexpected grouping.

Per the ApiProfileMin type, id is a required string. Since nullish coalescing (??) only catches null/undefined and not empty strings, if id is "", it will be returned as the key. Multiple profiles with empty id would then share the same key and be incorrectly merged together.

If this is an intentional defensive measure against malformed data, consider using || instead of ?? to treat empty strings as "missing":

Optional: Use falsy check for defensive empty-string handling
 function getIdentityKey(profile: ApiProfileMin): string {
   return (
-    profile.id ??
-    profile.handle ??
-    profile.primary_address ??
+    profile.id ||
+    profile.handle ||
+    profile.primary_address ||
     `unknown-profile`
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`
around lines 26 - 33, getIdentityKey currently uses nullish coalescing
(profile.id ?? profile.handle ?? profile.primary_address ?? `unknown-profile`)
which will return empty strings as valid keys and can cause incorrect grouping
for ApiProfileMin entries with id === ""; change the logic in getIdentityKey to
treat empty strings as missing (e.g., use falsy checks or || instead of ??, or
explicitly check profile.id.trim() === "" before falling back) so that
handle/primary_address/`unknown-profile` are used when id is an empty string.

193-197: Redundant null/undefined check for profile.id.

Per ApiProfileMin, id is a required string (not nullable). The null/undefined check and String() conversion are unnecessary:

Simplify displayName logic
                   const displayName =
-                    profile.handle ??
-                    (profile.id === null || profile.id === undefined
-                      ? undefined
-                      : String(profile.id));
+                    profile.handle ?? profile.id;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`
around lines 193 - 197, The displayName assignment includes an unnecessary
null/undefined check and String() conversion for profile.id even though
ApiProfileMin defines id as a required string; replace the ternary block with a
simple fallback: use profile.handle ?? profile.id (remove the (profile.id ===
null || profile.id === undefined ? undefined : String(profile.id)) logic) so
displayName is either profile.handle or profile.id directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 60-62: The loop currently skips notifications when
getIdentityKey(n.related_identity) returns an empty string which silently drops
valid reactions; change the behavior in NotificationDropReactedGroup by
capturing the result of getIdentityKey for each notification (the const key =
getIdentityKey(n.related_identity) inside the for (const n of notifications)
loop) and if it is empty, use a deterministic fallback key (for example
"unknown-identity" or fallback to n.related_identity.profile?.handle ||
n.related_identity.id) and emit a console.warn or use the component logger to
record the missing key before grouping; ensure the rest of the grouping logic
uses this fallback rather than continuing the loop so notifications are not
lost.

In `@components/header/header-search/HeaderSearchModal.tsx`:
- Around line 333-351: The current matcher only tests each candidate field
independently (using hasCanonicalPageTokenMatch and
hasCanonicalPageTokenPrefixMatch over normalizedTitle, normalizedHref,
normalizedBreadcrumbs, normalizedSearchTerms), so queries spanning fields (e.g.,
"metrics health") miss matches; fix this by building and passing extra composite
searchable strings (e.g., join normalizedBreadcrumbs + normalizedTitle into
"network metrics health" and also shorter combinations like "metrics health")
into the same array you pass to the matcher and ensure the identical composite
list is produced and reused by getPageMatchPriority() so matching and ranking
remain aligned (update the construction logic where
normalizedTitle/normalizedBreadcrumbs/normalizedSearchTerms are assembled and
reference that composite-list in both the matcher call and
getPageMatchPriority).
- Around line 272-309: Add an explicit exact-href check so exact full-path
queries win before breadcrumb/token fallbacks: inside the checks array in
getPageMatchPriority (where normalizedTitle, hrefValues, hrefSegments,
normalizedQuery etc. are in scope), insert normalizedHref === normalizedQuery as
a high-priority entry (for example immediately after normalizedTitle ===
normalizedQuery) so an exact match on normalizedHref is found before any
breadcrumb/token-based checks or substring fallbacks.

---

Nitpick comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 26-33: getIdentityKey currently uses nullish coalescing
(profile.id ?? profile.handle ?? profile.primary_address ?? `unknown-profile`)
which will return empty strings as valid keys and can cause incorrect grouping
for ApiProfileMin entries with id === ""; change the logic in getIdentityKey to
treat empty strings as missing (e.g., use falsy checks or || instead of ??, or
explicitly check profile.id.trim() === "" before falling back) so that
handle/primary_address/`unknown-profile` are used when id is an empty string.
- Around line 193-197: The displayName assignment includes an unnecessary
null/undefined check and String() conversion for profile.id even though
ApiProfileMin defines id as a required string; replace the ternary block with a
simple fallback: use profile.handle ?? profile.id (remove the (profile.id ===
null || profile.id === undefined ? undefined : String(profile.id)) logic) so
displayName is either profile.handle or profile.id directly.

In `@contexts/TitleContext.tsx`:
- Around line 108-109: Remove the redundant aliases currentWaveId and
previousWaveId in TitleContext.tsx and inline currentWaveInUrl and
previousWaveInUrl directly where those aliases are used (e.g., in the
conditional checks around the currentWave/previousWave logic and the block
referenced near lines 125-128). Update any expressions or conditions that
reference currentWaveId/previousWaveId to use currentWaveInUrl/previousWaveInUrl
instead and delete the now-unused const declarations to reduce cognitive
overhead and unused variables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81208a81-e4f4-45fe-94ff-9f449e4f321b

📥 Commits

Reviewing files that changed from the base of the PR and between 3459faf and feb2de4.

📒 Files selected for processing (7)
  • __tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • __tests__/components/meme-calendar/meme-calendar.helpers.timezone.test.ts
  • __tests__/contexts/TitleContext.test.tsx
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
  • components/header/header-search/HeaderSearchModal.tsx
  • components/meme-calendar/meme-calendar.helpers.tsx
  • contexts/TitleContext.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • components/meme-calendar/meme-calendar.helpers.tsx
  • tests/contexts/TitleContext.test.tsx
  • tests/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • tests/components/meme-calendar/meme-calendar.helpers.timezone.test.ts

Comment thread components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx Outdated
Comment thread components/header/header-search/HeaderSearchModal.tsx
Comment thread components/header/header-search/HeaderSearchModal.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx (1)

46-59: Inconsistent empty-string handling for handle may cause malformed links and API calls.

Using ?? for handle (line 53) preserves empty strings "", while || for pfp/primary_address treats them as missing. If preferred.handle is "" and fallback.handle is valid, the merged profile retains the empty string.

Downstream, NotificationHeader creates href="/" with empty link text, and NotificationsFollowBtn calls identities//subscriptions—both malformed.

Consider using || consistently, or use getNonEmptyIdentityValue:

Proposed fix
   return {
     ...fallback,
     ...preferred,
-    handle: preferred.handle ?? fallback.handle,
-    // Keep an explicit empty handle, but treat blank avatar/address strings as
-    // missing so older identity data survives grouped merges.
+    // Treat blank strings as missing so older identity data survives grouped merges.
+    handle: getNonEmptyIdentityValue(preferred.handle) ?? fallback.handle,
     pfp: preferred.pfp || fallback.pfp,
     primary_address: preferred.primary_address || fallback.primary_address,
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`
around lines 46 - 59, mergeProfiles currently uses the nullish coalescing
operator for handle (preferred.handle ?? fallback.handle) which preserves empty
strings and leads to malformed links and API calls downstream (e.g.,
NotificationHeader and NotificationsFollowBtn); change the logic in
mergeProfiles to treat empty strings as missing by using a truthy check
(preferred.handle || fallback.handle) or a helper like getNonEmptyIdentityValue
for handle so empty "" falls back to fallback.handle, keeping pfp and
primary_address behavior consistent with handle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 188-203: The single-reactor rendering path must validate
singleReactor.handle the same way the multi-reactor path does: before passing
singleReactor to NotificationHeader and NotificationsFollowBtn, check
singleReactor.handle and use profile.handle ? `/${profile.handle}` : undefined
(or fall back to rendering the multi-reactor layout) so you don't pass "/null"
or malformed hrefs like "identities//subscriptions"; update the
NotificationDropReactedGroup single-reactor branch to perform this guard and use
the validated/normalized handle value when constructing NotificationHeader and
NotificationsFollowBtn props.

In `@components/header/header-search/HeaderSearchModal.tsx`:
- Around line 301-338: pageMatchesQuery currently performs href substring checks
too late, so path-like partial queries (e.g., "/network/he") can lose to
token-based title matches; update the checks array inside pageMatchesQuery to
move full-href prefix and substring tests (e.g.,
normalizedHref.startsWith(normalizedQuery) and
normalizedHref.includes(normalizedQuery) or similar checks against
hrefValues/hrefSegments) before any token-based fallbacks (functions like
hasCanonicalPageTokenMatch, hasCanonicalPageTokenPrefixMatch,
hasExactCanonicalPageTokenMatch), and only run these full-href checks for
path-like queries (detect via normalizedQuery containing "/" or starting with
"/") so we prioritize direct path matches before token-based ranking.

---

Nitpick comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 46-59: mergeProfiles currently uses the nullish coalescing
operator for handle (preferred.handle ?? fallback.handle) which preserves empty
strings and leads to malformed links and API calls downstream (e.g.,
NotificationHeader and NotificationsFollowBtn); change the logic in
mergeProfiles to treat empty strings as missing by using a truthy check
(preferred.handle || fallback.handle) or a helper like getNonEmptyIdentityValue
for handle so empty "" falls back to fallback.handle, keeping pfp and
primary_address behavior consistent with handle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5fbabda0-c9df-48c1-bb8f-e60cf35826a4

📥 Commits

Reviewing files that changed from the base of the PR and between feb2de4 and 909082b.

📒 Files selected for processing (5)
  • __tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • __tests__/components/header/HeaderSearchModal.test.tsx
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
  • components/header/header-search/HeaderSearchModal.tsx
  • contexts/TitleContext.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • contexts/TitleContext.tsx

Comment thread components/header/header-search/HeaderSearchModal.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 74-78: getIdentityKey/identityKey currently returns a single
preferred key which can split the same person across buckets; modify the
grouping logic in NotificationDropReactedGroup (where identityKey and key are
computed) to first attempt to match an existing byUser entry by any known alias
(n.related_identity.id, n.related_identity.handle,
n.related_identity.primary_address) before creating a new key; if any alias
matches an existing bucket use that bucket's key, otherwise fall back to the
existing identityKey/`unknown-identity-${n.id}` behavior so identical reactors
are grouped even when different fields are populated.
- Around line 96-106: The merge currently discards previously accumulated
profile data when folding an older notification: in the block that computes
isNewer, latest, fallbackIdentity and calls byUser.set, ensure you preserve
existing.identity when n is older than existing.latest by using
existing.identity (not raw existing.related_identity) as one of the inputs to
mergeProfiles; i.e., when isNewer is false set identity =
mergeProfiles(existing.identity, n.related_identity) (and when true keep
mergeProfiles(latest.related_identity, existing.identity)) so the accumulated
handle/pfp from existing.identity is retained before calling byUser.set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bcfa2c0a-87b6-4e25-bd53-01ffaf4a3b0d

📥 Commits

Reviewing files that changed from the base of the PR and between 909082b and fb4b7d9.

📒 Files selected for processing (4)
  • __tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • __tests__/components/header/HeaderSearchModal.test.tsx
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
  • components/header/header-search/HeaderSearchModal.tsx
✅ Files skipped from review due to trivial changes (1)
  • tests/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx

Comment thread components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx Outdated
Comment thread components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx (1)

88-94: ⚠️ Potential issue | 🟠 Major

Fold all matched alias buckets into one byUser entry.

This still misses the case where two partial buckets already exist before a later notification connects them. Here you pick only the first alias hit, update that bucket, and repoint aliasToKey, but the other byUser entry is never merged away, so the same reactor can still render twice.

Possible direction
-    const matchingKey = getIdentityAliases(n.related_identity).find((alias) =>
-      aliasToKey.has(alias)
-    );
-    const key =
-      matchingKey === undefined
-        ? fallbackKey
-        : (aliasToKey.get(matchingKey) ?? fallbackKey);
+    const matchedKeys = Array.from(
+      new Set(
+        getIdentityAliases(n.related_identity)
+          .map((alias) => aliasToKey.get(alias))
+          .filter((mappedKey): mappedKey is string => mappedKey !== undefined)
+      )
+    );
+    const key = matchedKeys[0] ?? fallbackKey;
+    // Merge/delete any additional matched buckets before applying `n`,
+    // so `byUser` only keeps one entry per logical reactor.

Also applies to: 104-132

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`
around lines 88 - 94, When assigning a bucket for a reactor you currently only
pick the first matching alias (matchingKey) and repoint aliasToKey, which leaves
other pre-existing byUser buckets unmerged and causes duplicate renders;
instead, collect all aliases from getIdentityAliases(n.related_identity) that
exist in aliasToKey, determine one canonical key (e.g., fallbackKey or the first
existing key), then merge every other bucket's contents from byUser into that
canonical byUser entry, update aliasToKey for each merged alias to point to the
canonical key, and delete the now-empty/duplicate byUser entries so all aliases
fold into a single bucket.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 258-270: Normalize profile.handle once using
getNonEmptyIdentityValue and use that normalized value when building href,
displayName, title, and fallback in NotificationDropReactedGroup (instead of
using profile.handle directly); compute const normalizedHandle =
getNonEmptyIdentityValue(profile.handle) (or similar) and then set href =
normalizedHandle ? `/${normalizedHandle}` : undefined, displayName =
normalizedHandle ?? profile.id, title = displayName || undefined, and fallback =
normalizedHandle?.slice(0,2).toUpperCase() ?? "?" so empty or whitespace-only
handles no longer produce invalid hrefs or empty avatar/tooltips.

---

Duplicate comments:
In
`@components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx`:
- Around line 88-94: When assigning a bucket for a reactor you currently only
pick the first matching alias (matchingKey) and repoint aliasToKey, which leaves
other pre-existing byUser buckets unmerged and causes duplicate renders;
instead, collect all aliases from getIdentityAliases(n.related_identity) that
exist in aliasToKey, determine one canonical key (e.g., fallbackKey or the first
existing key), then merge every other bucket's contents from byUser into that
canonical byUser entry, update aliasToKey for each merged alias to point to the
canonical key, and delete the now-empty/duplicate byUser entries so all aliases
fold into a single bucket.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23df69c7-8759-4abf-8828-b7301900a10c

📥 Commits

Reviewing files that changed from the base of the PR and between fb4b7d9 and 9823d22.

📒 Files selected for processing (2)
  • __tests__/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx
  • components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/components/brain/notifications/drop-reacted/NotificationDropReactedGroup.test.tsx

Comment thread components/brain/notifications/drop-reacted/NotificationDropReactedGroup.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@prxt6529 prxt6529 merged commit 8d32241 into main Mar 31, 2026
7 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants